// Copyright 2019-2020 Gohilla.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

import 'dart:typed_data';

import 'package:collection/collection.dart';
import 'package:cryptography/cryptography.dart';
import 'package:cryptography/src/cryptography/_cupertino_der.dart';

/// Public key of _P-256_ / _P-384_ / _P-521_ key pair.
///
/// There are many formats for storing elliptic curve key parameters.
/// If you are encoding/decoding JWK (JSON Web Key) format, use
/// [package:jwk](https://pub.dev/packages/jwk).
///
/// ## Related classes
///   * [EcKeyPair]
///   * [EcKeyPairData]
///
/// ## Algorithms that use this class
///   * [Ecdh]
///   * [Ecdsa]
///
class EcPublicKey extends PublicKey {
  /// Elliptic curve public key component `x`.
  final List<int> x;

  /// Elliptic curve public key component `y`.
  final List<int> y;

  @override
  final KeyPairType type;

  EcPublicKey({required this.x, required this.y, required this.type});

  @override
  int get hashCode =>
      const ListEquality<int>().hash(x) ^
      const ListEquality<int>().hash(y) ^
      type.hashCode;

  @override
  bool operator ==(other) =>
      other is EcPublicKey &&
      const ListEquality<int>().equals(x, other.x) &&
      const ListEquality<int>().equals(y, other.y) &&
      type == other.type;

  /// Constructs DER encoding of this public key.
  ///
  /// The implementation generates DER encodings identical to those generated by
  /// Apple CryptoKit.
  Uint8List toDer() {
    final config = CupertinoEcDer.get(type);
    final numberLength = config.numberLength;
    final x = this.x;
    if (x.length != numberLength) {
      throw StateError(
        '$this parameter "x" should have $numberLength bytes, not ${x.length}',
      );
    }
    final y = this.y;
    if (y.length != numberLength) {
      throw StateError(
        '$this parameter "y" should have $numberLength bytes, not ${y.length}',
      );
    }
    return Uint8List.fromList([
      ...config.publicKeyPrefix,
      ...x,
      ...y,
    ]);
  }

  @override
  String toString() =>
      'EcPublicKey(x: [${x.join(',')}], y: [${y.join(',')}], type: $type)';

  /// Parses DER-encoded EC public key.
  ///
  /// Currently this is implemented only for very specific inputs: those
  /// generated by Apple's CryptoKit. Apple could decide to change their
  /// implementation in future (though it has no reason to). Therefore we would
  /// like to transition to a proper ASN.1 decoder.
  static EcPublicKey parseDer(List<int> der, {required KeyPairType type}) {
    // Parsing of CryptoKit generated keys:
    // Unfortunately our current solutions is not future proof. Apple may change
    // the format in the future. We want to transition to a proper ASN.1 decoder.
    final config = CupertinoEcDer.get(type);
    final prefix = config.publicKeyPrefix;
    final numberLength = config.numberLength;
    if (!bytesStartsWith(der, prefix, 0)) {
      throw UnsupportedError(
        'Your version of package:cryptography supports only specific DER encodings from Apple CryptoKit',
      );
    }
    if (der.length != prefix.length + 2 * numberLength) {
      throw UnsupportedError(
        'Your version of package:cryptography supports only specific DER encodings from Apple CryptoKit',
      );
    }
    final xIndex = prefix.length;
    final yIndex = xIndex + numberLength;
    final x = Uint8List.fromList(der.sublist(
      xIndex,
      yIndex,
    ));
    final y = Uint8List.fromList(der.sublist(
      yIndex,
      yIndex + numberLength,
    ));
    return EcPublicKey(x: x, y: y, type: type);
  }
}
